import socket
from threading import Thread
import bpy
import os
import sys
import time
import math

ellipsoidVertices = None
ellipsoidFaces = None
coneVertices = None
coneFaces = None
cylinderVertices = None
cylinderFaces = None

#Empty the scene:
def eraseScene():
	print ('Method start: eraseScene()')
	
	try:
		for shape in bpy.data.scenes['Scene'].objects:
			name = shape.name[0:4]
			if (name != 'hand' ):
				bpy.data.scenes['Scene'].objects.unlink(shape)
	except:
		print ('An exception was catched in method eraseScene(). TYPE: ' + sys.exc_info()[0])
		print ('This needs to be examined further!')
			
	print ('Method end: eraseScene()')
		
#Rotate the scene:
def rotateScene(angle):
	print ('Method start: rotateScene()')
	
	sinA = math.sin(angle)
	cosA = math.cos(angle)
	
	try:
		for shape in bpy.data.scenes['Scene'].objects:
			name = shape.name[0:5]
			if (name == 'SHAPE' ):
				for v in shape.data.vertices:
					vCoordinates = v.co
					vX = vCoordinates[0]
					vY = vCoordinates[1]
					vZ = vCoordinates[2]
					vXR = (vX*cosA)+(vY*sinA)
					vYR = (vX*sinA*-1)+(vY*cosA)
					v.co = (vXR,vYR,vZ)
			if (name == 'CURVE' ):
				for p in shape.data.splines[0].bezier_points:
					pCoordinates = p.co
					pX = pCoordinates[0]
					pY = pCoordinates[1]
					pZ = pCoordinates[2]
					pXR = (pX*cosA)+(pY*sinA)
					pYR = (pX*sinA*-1)+(pY*cosA)
					p.co = (pXR,pYR,pZ)
	except:
		print ('An exception was catched in method rotateScene(). TYPE: ' + str(sys.exc_info()[0]))
		print ('This needs to be examined further!')
			
	print ('Method end: rotateScene()')

#Translate the scene:
def translateScene(x,y,z):
	print ('Method start: translateScene()')
	
	try:
		for shape in bpy.data.scenes['Scene'].objects:
			name = shape.name[0:5]
			if (name == 'SHAPE' ):
				for v in shape.data.vertices:
					vCoordinates = v.co
					vX = vCoordinates[0]
					vY = vCoordinates[1]
					vZ = vCoordinates[2]
					v.co = (vX+x,vY+y,vZ+z)
			if (name == 'CURVE' ):
				for p in shape.data.splines[0].bezier_points:
					pCoordinates = p.co
					pX = pCoordinates[0]
					pY = pCoordinates[1]
					pZ = pCoordinates[2]
					p.co = (pX+x,pY+y,pZ+z)
	except:
		print ('An exception was catched in method translateScene(). TYPE: ' + str(sys.exc_info()[0]))
		print ('This needs to be examined further!')
			
	print ('Method end: translateScene()')
	
	#Rotate the scene:
def zoomScene(p):
	print ('Method start: zoomScene()')
	
	zoomFactor = float(p/100)
	if (zoomFactor < 0.1):
		zoomFactor = 0.1
		print ('Zoom was too low (' + p + '%), changed to 10%')
	if (zoomFactor > 3):
		zoomFactor = 3
		print ('Zoom was too high (' + p + '%), changed to 300%')
	
	try:
		for shape in bpy.data.scenes['Scene'].objects:
			name = shape.name[0:4]
			if (name != 'hand' ):
				shape.delta_scale = (zoomFactor,zoomFactor,zoomFactor)		
	except:
		print ('An exception was catched in method zoomScene(). TYPE: ' + str(sys.exc_info()[0]))
		print ('This needs to be examined further!')
			
	print ('Method end: zoomScene()')
		
#returns the current R, G and B values to draw in Blender.
def getColor():
	print ('Method start: getColor()')
	
	try:
		folder = repr(os.getcwd()).replace("'","")
		filePath = folder + '\\\\OBJ\\\\color.txt'
		
		colorfile = open(filePath) 
		colors = colorfile.readline().split()
		R = float(colors[0])
		G = float(colors[1])
		B = float(colors[2])
		colorfile.close()
	except:
		print ('An exception was catched in method getColor(). TYPE: ' + str(sys.exc_info()[0]))
		print ('This needs to be examined further!')
			
	print ('Method end: getColor()')
	return (R,G,B)

#Function that sets the diffuse color Blender will draw in.
def setColor(r,g,b):
	print ('Method start: setColor()')
	
	try:
		folder = repr(os.getcwd()).replace("'","")
		filePath = folder + '\\\\OBJ\\\\color.txt'
		
		colorfile = open(filePath,'w')
		colorfile.write(str(r) + ' ' + str(g) + ' ' + str(b))
		colorfile.close()
	except:
		print ('An exception was catched in method setColor(). TYPE: ' + sys.exc_info()[0])
		print ('This needs to be examined further!')
			
	print ('Method end: setColor()')
	
#Function that reads data from an .obj file.	
def getDataFromOBJ(fileName,datatype):
	folder = repr(os.getcwd()).replace("'","")
	filePath = folder + '\\\\OBJ\\\\' + fileName

	objfile = open(filePath)
	lines = objfile.readlines()
	
	data = []
	for line in lines:
		words = line.split()
		if (datatype == 'vertices') and (words[0] == "v"):
			x = float(words[1])
			z = float(words[2])
			y = float(words[3])
			data.append([x, y, z])
		elif (datatype == 'faces') and (words[0] == "f"):
			x = int(words[1])-1
			y = int(words[2])-1
			z = int(words[3])-1	
			data.append([x, y, z])
			
	objfile.close()		
	return data
	
#Function that draws a sphere or ellipsoid.
def drawEllipsoid(x,y,z,r,sx,sy,sz):
	ellipsoid = createMesh('SHAPE_Ellipsoid', (0,0,0), ellipsoidVertices, ellipsoidFaces)
	
	for v in ellipsoid.data.vertices:
		vCoordinates = v.co
		vX = vCoordinates[0]
		vY = vCoordinates[1]
		vZ = vCoordinates[2]
		
		dX = (vX*sx*r) + x
		dY = (vY*sy*r) + y
		dZ = (vZ*sz*r) + z
		
		v.co = (dX,dY,dZ)
	
#Function that draws a cone.
def drawCone(x,y,z,r,h):
	cone = createMesh('SHAPE_Cone', (0,0,0), coneVertices, coneFaces)
	for v in cone.data.vertices:
		vCoordinates = v.co
		vX = vCoordinates[0]
		vY = vCoordinates[1]
		vZ = vCoordinates[2]
		
		dX = (vX*r) + x
		dY = (vY*r) + y
		dZ = (vZ*(h/2)) + z + (h/2)
		
		v.co = (dX,dY,dZ)
	
#Function that draws a pyramid
def drawPyramid(x,y,z,b,h):
	pyramidVertices = ((x,y,z+h),(x+b/2,y-b/2,z),(x+b/2,y+b/2,z),(x-b/2,y+b/2,z),(x-b/2,y-b/2,z))
	pyramidFaces = ((1,2,3,4),(0,2,3),(0,1,2),(0,4,1),(0,3,4))
	createMesh('SHAPE_Pyramid', (0,0,0), pyramidVertices, pyramidFaces)
	
#Function that draws a cylinder.
def drawCylinder(x,y,z,r,h):
	cylinder = createMesh('SHAPE_Cylinder', (0,0,0), cylinderVertices, cylinderFaces)
	for v in cylinder.data.vertices:
		vCoordinates = v.co
		vX = vCoordinates[0]
		vY = vCoordinates[1]
		vZ = vCoordinates[2]
		
		dX = (vX*r) + x
		dY = (vY*r) + y
		dZ = (vZ*(h/2)) + z + (h/2)
		
		v.co = (dX,dY,dZ)
#Function that draws a cube.
def drawCube(l,b,n,r,t,f):	
	cubeVertices = ((l,b,f), (l,b,n), (r,b,n), (r,b,f), (l,t,f), (l,t,n), (r,t,n), (r,t,f))
	cubeFaces = ((0,1,2,3),(4,5,6,7),(0,1,5,4),(0,3,7,4),(1,2,6,5),(2,3,7,6))
	createMesh('SHAPE_Box', (0,0,0), cubeVertices, cubeFaces)
	
#Function that draws a roof.
def drawRoof(x,y,z,b,h,d):	
	verts = ((x,y,z), (x,y-(b/2),z-h), (x,y+(b/2),z-h), (x-d,y,z), (x-d,y-(b/2),z-h), (x-d,y+(b/2),z-h))
	faces = ((0,1,2),(0,2,5,3),(3,5,4),(0,3,4,1),(1,2,5,4))
	createMesh('SHAPE_Roof', (0,0,0), verts, faces)
	
#Function that starts a new curve at the given x,y,z
def activateCurve(x,y,z):
	print ('Starting method activateCurve...')
	
	displayPlayIconThread().start()
	
	try:
		activeCurve = bpy.data.curves['CURVE_ACTIVE']
		activeCurve.name = 'CURVE_DEACTIVE'
	except KeyError:
		pass
	
	curve = bpy.data.curves.new('CURVE_ACTIVE', 'CURVE')
	curveObject = bpy.data.objects.new('CURVE_OBJECT', curve)
	bpy.context.scene.objects.link(curveObject)
	curve.dimensions = '3D'
	curve.bevel_depth = 0.1
	curve.bevel_resolution = 30
	curve.fill_mode = 'FULL'
	curveObject.show_name = False
	
	#Make the curve a color
	color = getColor()
	material = bpy.data.materials.new('Diffuse')
	material.diffuse_color = (color[0],color[1],color[2])
	curve.materials.append(material)

	#Create spline and set Bezier control points
	spline = curve.splines.new('BEZIER')
	bpt = spline.bezier_points[0]
	bpt.handle_left_type = 'AUTO'
	bpt.handle_right_type = 'AUTO'
	bpt.co = (x,y,z)
	
#Function that starts a new curve at the given x,y,z
def deactivateCurve():
	print ('Starting method deactivateCurve...')
	
	displayStopIconThread().start()
	
	try:
		activeCurve = bpy.data.curves['CURVE_ACTIVE']
		activeCurve.name = 'CURVE_DEACTIVE'
	except KeyError:
		print('ERROR CATCHED: trying to deactivate a curve, but no curve is activated!')	
	
#Function that appends to the last active line
def appendToCurve(x,y,z):
	print ('Starting method appendToCurve...')
	
	try:
		activeCurve = bpy.data.curves['CURVE_ACTIVE']
	except KeyError:
		print('ERROR CATCHED: trying to append to a curve, but no curve is activated!')
	
	spline = activeCurve.splines[0]
	amountOfPoints = len(spline.bezier_points)
	spline.bezier_points.add(1)
	bpt = spline.bezier_points[amountOfPoints]
	bpt.handle_left_type = 'AUTO'
	bpt.handle_right_type = 'AUTO'
	bpt.co = (x,y,z)
	
#Function that creates a mesh in Blender.
def createMesh(name, origin, verts, faces):
	# Create mesh and object.
	me = bpy.data.meshes.new(name+'Mesh')
	material = bpy.data.materials.new(name+'Diffuse')
	color = getColor()
	material.diffuse_color = (color[0],color[1],color[2])
	me.materials.append(material)
	
	ob = bpy.data.objects.new(name, me)
	ob.location = origin
	ob.show_name = False
	
	# Link object to scene.
	bpy.context.scene.objects.link(ob)
	
	# Create mesh from given verts, edges, faces. Either edges or faces should be [], or you ask for problems!
	me.from_pydata(verts, [], faces)
	for polygon in me.polygons:
		polygon.use_smooth = True
	
	# Update mesh with new data.
	me.update(calc_edges=True)
	return ob

#Function that draws small red spheres which represent the hands of the user
def createHands():
	for i in range(20):
		scaleFactor = 0.01*(i+1)
		
		nameL = 'hand_L' + str(i)
		handObject = createMesh(nameL, (0,0,0), ellipsoidVertices, ellipsoidFaces)
		handObject.delta_scale = (scaleFactor,scaleFactor,scaleFactor)
		handMeshMaterial = bpy.data.meshes[nameL+'Mesh'].materials[nameL+'Diffuse']
		handMeshMaterial.diffuse_color = (0.0,1.0,0.0)
		
		nameR = 'hand_R' + str(i)
		handObject = createMesh(nameR, (0,0,0), ellipsoidVertices, ellipsoidFaces)
		handObject.delta_scale = (scaleFactor,scaleFactor,scaleFactor)
		handMeshMaterial = bpy.data.meshes[nameR+'Mesh'].materials[nameR+'Diffuse']
		handMeshMaterial.diffuse_color = (0.0,1.0,0.0)
		
def createIcons():
	name = 'handPlayIcon'
	me = bpy.data.meshes.new(name+'Mesh')
	material = bpy.data.materials.new(name+'Diffuse')
	material.diffuse_color = (0.0,1.0,0.0)
	me.materials.append(material)	
	ob = bpy.data.objects.new(name, me)
	ob.location = (0,0,0)
	ob.show_name = False
	me.from_pydata([(7,2.3,3.1),(7,-1.7,5),(7,-1.6,1)], [], [(0,1,2)])
	me.update(calc_edges=True)
	
	name = 'handStopIcon'
	me = bpy.data.meshes.new(name+'Mesh')
	material = bpy.data.materials.new(name+'Diffuse')
	material.diffuse_color = (1.0,0.0,0.0)
	me.materials.append(material)	
	ob = bpy.data.objects.new(name, me)
	ob.location = (0,0,0)
	ob.show_name = False
	me.from_pydata([(7,-1.8,4.7),(7,-1.9,1.3),(7,1.9,1.3),(7,1.8,4.7)], [], [(0,1,2,3)])
	me.update(calc_edges=True)

#Function that moves the hands to new x,y,z locations		
def moveHandsOnePosition(leftX,leftY,leftZ,rightX,rightY,rightZ):
	nextLeftLocation = (leftX,leftY,leftZ)
	nextRightLocation = (rightX,rightY,rightZ)
	
	for i in range(20):
		currentLeft = bpy.data.objects['hand_L' + str(19-i)]
		currentLeftLocation = (currentLeft.location[0],currentLeft.location[1],currentLeft.location[2])
		currentLeft.location = nextLeftLocation
		nextLeftLocation = currentLeftLocation
		
		currentRight = bpy.data.objects['hand_R' + str(19-i)]
		currentRightLocation = (currentRight.location[0],currentRight.location[1],currentRight.location[2])
		currentRight.location = nextRightLocation
		nextRightLocation = currentRightLocation

#Function that shows the user how to draw a cube		
def tutorialCube():
	print ('starting tutorialcube, time is: ' + str(time.time()))

	for i in range(20):
		nameL = 'hand_L' + str(i)
		handMeshMaterial = bpy.data.meshes[nameL + 'Mesh'].materials[nameL + 'Diffuse']
		handMeshMaterial.diffuse_color = (1.0, 0.0, 0.0)
		nameR = 'hand_R' + str(i)
		handMeshMaterial = bpy.data.meshes[nameR + 'Mesh'].materials[nameR + 'Diffuse']
		handMeshMaterial.diffuse_color = (1.0, 0.0, 0.0)
		moveHandsOnePosition(5, 0, 5, 5, 0, 5)

	time.sleep(0.5)
	
	for i in range(1,11):
		delta = i/2
		moveHandsOnePosition(5, -delta, 5, 5, delta, 5)
		time.sleep(0.075)
		
	for i in range(1,11):
		delta = 5-(i/2)
		moveHandsOnePosition(5, -5, delta, 5, 5, delta)
		time.sleep(0.075)
		
	for i in range(1,11):
		delta = 5-i/2
		moveHandsOnePosition(5, -delta, 0, 5, delta, 0)
		time.sleep(0.075)
		
	for i in range(19):
		moveHandsOnePosition(5,0,0,5,0,0)
		time.sleep(0.075)
		
	time.sleep(0.5)

	for i in range(20):
		nameL = 'hand_L' + str(i)
		handMeshMaterial = bpy.data.meshes[nameL + 'Mesh'].materials[nameL + 'Diffuse']
		handMeshMaterial.diffuse_color = (0.0, 1.0, 0.0)
		nameR = 'hand_R' + str(i)
		handMeshMaterial = bpy.data.meshes[nameR + 'Mesh'].materials[nameR + 'Diffuse']
		handMeshMaterial.diffuse_color = (0.0, 1.0, 0.0)

	print ('ending tutorialcube, time is: ' + str(time.time()))
	
#Function parses the data in the received messages.
def parseData(data):
	amount = 0
	try:
		if (data[0] == 'HANDS'):
			moveHandsOnePosition(float(data[1]),float(data[2]),float(data[3]),float(data[4]),float(data[5]),float(data[6]))
			amount = 7
		elif (data[0] == 'CUBE'):
			drawCube(float(data[1]),float(data[2]),float(data[3]),float(data[4]),float(data[5]),float(data[6]))
			amount = 7
		elif (data[0] == 'ELLIPSOID'):
			drawEllipsoid(float(data[1]),float(data[2]),float(data[3]),float(data[4]),float(data[5]),float(data[6]),float(data[7]))
			amount = 8
		elif (data[0] == 'CONE'):
			drawCone(float(data[1]),float(data[2]),float(data[3]),float(data[4]),float(data[5]))
			amount = 6
		elif (data[0] == 'CYLINDER'):
			drawCylinder(float(data[1]),float(data[2]),float(data[3]),float(data[4]),float(data[5]))
			amount = 6
		elif (data[0] == 'PYRAMID'):
			drawPyramid(float(data[1]),float(data[2]),float(data[3]),float(data[4]),float(data[5]))
			amount = 6
		elif (data[0] == 'ROOF'):
			drawRoof(float(data[1]),float(data[2]),float(data[3]),float(data[4]),float(data[5]),float(data[6]))
			amount = 7
		elif (data[0] == 'CURVE_ACTIVATE'):
			activateCurve(float(data[1]),float(data[2]),float(data[3]))
			amount = 4
		elif (data[0] == 'CURVE_DEACTIVATE'):
			deactivateCurve()
			amount = 1
		elif (data[0] == 'CURVE_APPEND'):
			appendToCurve(float(data[1]),float(data[2]),float(data[3]))
			amount = 4
		elif (data[0] == 'ERASE'):
			eraseScene()
			amount = 1
		elif (data[0] == 'COLOR'):
			setColor(float(data[1]),float(data[2]),float(data[3]))
			amount = 4
		elif (data[0] == 'TUTORIAL_CUBE'):
			tutorialCube()
			amount = 1
		elif (data[0] == 'ROTATE_SCENE'):
			rotateScene(float(data[1]))
			amount = 2
		elif (data[0] == 'TRANSLATE_SCENE'):
			translateScene(float(data[1]),float(data[2]),float(data[3]))
			amount = 4
		elif (data[0] == 'ZOOM_SCENE'):
			zoomScene(float(data[1]))
			amount = 2
		else:
			print('While parsing data, the first argument ("' + data[0] + '") did not match any known command. I will ignore this argument and continue with other arguments (if any)')
			amount = 1
		
		if (len(data) > amount):
			print('While parsing data, there were unused arguments at the end. I will try to parse them.')
			parseData(data[amount:])
		
	except ValueError:
		print ('A VALUE ERROR exception was catched in method parseData(). It happened with a ' + data[0] + ' message.')
		print ('Here are all the arguments of the message:')
		for arg in data:
			print (arg)
	except:
		print ('An exception of type ' + str(sys.exc_info()[0]) + 'was catched in method parseData(). It happened with a ' + data[0] + ' message.')
		print ('Here are all the arguments of the message:')
		for arg in data:
			print (arg)
	
	
class displayPlayIconThread(Thread):
	def run(self):
		name = 'handPlayIcon'
		playIcon = bpy.data.objects[name]
		playIconMaterial = bpy.data.meshes[name+'Mesh'].materials[name+'Diffuse']
		bpy.context.scene.objects.link(playIcon)
		time.sleep(0.3)
		playIconMaterial.diffuse_color = (0.3,0.3,0.3)
		time.sleep(0.3)
		playIconMaterial.diffuse_color = (0.0,1.0,0.0)
		time.sleep(0.3)
		bpy.context.scene.objects.unlink(playIcon)
		
class displayStopIconThread(Thread):
	def run(self):
		name = 'handStopIcon'
		stopIcon = bpy.data.objects[name]
		stopIconMaterial = bpy.data.meshes[name+'Mesh'].materials[name+'Diffuse']
		bpy.context.scene.objects.link(stopIcon)
		time.sleep(0.3)
		stopIconMaterial.diffuse_color = (0.3,0.3,0.3)
		time.sleep(0.3)
		stopIconMaterial.diffuse_color = (1.0,0.0,0.0)
		time.sleep(0.3)
		bpy.context.scene.objects.unlink(stopIcon)			
		
	
class socketListener(Thread):
	def run(self):
		#Make a connection
		s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
		s.connect(('localhost', 3000))
		print ('Ready for action!')
		
		#Keep listening in this thread forever
		while(True):
			#print ('Blender is waiting for a command')
			dataBytes = s.recv(1024)
			dataString = dataBytes.decode('utf-8')
			print ('Blender received this command: ' + dataString)
			data = dataString.split();
			parseData(data)
			
			
#Import the necessary .obj mesh data
ellipsoidVertices = getDataFromOBJ('sphere.obj','vertices')
ellipsoidFaces = getDataFromOBJ('sphere.obj','faces')
coneVertices = getDataFromOBJ('cone.obj','vertices')
coneFaces = getDataFromOBJ('cone.obj','faces')
cylinderVertices = getDataFromOBJ('cylinder.obj','vertices')
cylinderFaces = getDataFromOBJ('cylinder.obj','faces')

#create hands
createHands()
createIcons()

#Start the socket communication thread
socketListener().start()
